밑의 코드 또한 continuation을 사용한다. c에는 cont가 없으면 a로 초기화 하고, 있으면 있던것을 쓴다.
continuation func은 return 값으로 continuation을 반환
while문의 조건 안에서는 반복적인 일 (밑의 코드의 주석으로 실행으로 표시한 부분) 이 이터레이션이다.
이터레이션으로 보면 while의 조건이 hasNext continuation(3,cont)가 next 부분임. 그러므로 이런 continuation의 행동은 이터레이션에 수련하게됨.
continuation을 쓰고 있는 코드의 분할을 자동으로 만들어 주는 것이 코틀린에서 고유명사 sequence이다. continuation 구문을 자동으로 만들어주는 이터레이션 생성기이다.
class Cont<T>{var state =0var isCompleted =falsevar result: T?=nullfunresume(v: T){
state++
result = v
}funcomplete(v: T){
isCompleted =true
result = v
}}funcontinuation1(a: Int, cont: Cont<Int>?=null)= run {var v: Int
val c =if(cont ==null){
v = a
Cont()}else{
v = cont.result!!
cont
}//본문when(c.state){0->{
v++println("state $v")
c.resume(v)}1->{
v++println("state $v")
c.resume(v)}else->{
v++println("state $v")
c.complete(v)}}
c
}funmain(){//실행var cont =continuation1(3)while(!cont.isCompleted){
cont =continuation1(3, cont)}println(cont.result)}
이렇게 하면 만들 수 있음.
continuation은 코드에 없으나 지가 알아서 만들것임
yield() 를 쓸 때마다 iteration이 나눠지는것임
왜 이렇게 짰는데 continuation이 생겨나는지는 컴파일러가 기능을 수행하기 때문이다.
우리는 sync block 형태로 짜길 원한다 이게 이해하기 쉽다.
sequence란 block이 나오면 밑과 같이 짜면 컴파일러는 위의 코드처럼 만들어준다. 밑은 컴파일러가 하는것이다.
continuation 객체를 만들고
continuation 객체를 호출하는 형태로 변경
안의 코드도 yield를 기준으로 각각의 케이스를 다른 스테이트로 나눠서 코드를 위의 when과 같이 바꿔줌
val s = sequence {var v =3
v++println('state $v")yield(v)
v++println('state $v")yield(v)
v++println('state $v")yield(v)
v++}println(s.last())
컴파일러는 sequence란 함수에 무슨 비밀이 있어서 이렇게 나눌 수 있는게 아니라 실제 sequence라는 애는 sequence context를 가지며 sequence context에 있는 메소드 yield를 호출 한것
sequence는 수신함수(확장함수)이기 때문에 context로 sequence context를 받아 들임.
sequence context 안의 메소드중 yield가 있기 때문에 sequence에서 사용할 수 있는것이다. 사실 this.yield임(왜 this를 생랴이 가능할까 ?)
밑의 코드중 가장 중요한 부분은 suspendCoroutineUninterceptedOrReturn { c-> 부분이다.
위의 c가 continuation이다.
여기서 나오는 suspend는 위의 코드에서 when에 나오는 하나의 섹션을 의미한다. 한번에 이 코드를 실행하지 않고 분할해서 나눠줘야하는 지점을 suspend라고 부름
위의 함수는 yield() 전까지 리턴이 된 상태임. 일반 적인 함수는 처음부터 시작해서 끝에서 끝나는데 코루틴이라는 형태의 함수들은 중간부터 중간을 리턴할 뿐만 아니라 중간부터 시작해서 중간을 리턴할수 있음 위의 코드와 같이 바뀌니까
각 섹션의 yield()가 일어나기 전까지의 것 하나하나를 suspend라고 한다 함수가 끝까지 실행되지 않고 멈춘다는 것임 중간 리턴 때 멈춤
그다음 suspend 된걸 재개하는데 이걸 resume 한다
밑의 코드는 nextStep을 현재의 continuation으로 리턴한 것 처럼 continuation 리턴에서 nextStep은 여기서 안보이지만 sequence context의 속성인데 얘를 다음번 continuation으로 잡아주고 리턴 값을 suspend상태로 보내주는 것이다. yield하면 suspend이니까
실제로 컴파일 타임에 이 코드를 나누게 되는 비밀은 suspendCoroutineUninterceptedOrReturn { c- >} 이다
위의 것이 컴파일러가 직접 코드를 나누게 명령내리는 컴파일러 전용 명령이다.
위의 것이 각각 continuation state로 나뉘어 지는 것이다. continuation state 1,2,3로나뉘어 지는 이유가 suspendCoroutineUninterceptedOrReturn얘임
위의 것은 suspendCoroutine 로 부터 파생 되는데 내용을 보면 구현이 internal로 되있음 언어에 맞춰 시스템이 구현 한것이고 개발자가 할게 아님 컴파일러가 처리할 것임. suspendCoroutineUninterceptedOrReturn 지점을 기준으로 분해시키라고 컴파일러에게 명령을 내리는 것임 이곳이 suspend pointer라고 yield가 나올때 마다 컴파일러에게 시켜서 suspend pointer 마다 분해를 시키는것 그때마다 분할 하자마자 continuation이 생겨나는 것임 왜 continuation을 인자로 받는지는 시스템이 날라주고 시스템이 continuation을 만들기 때문.
아까 위의 코드에선 우리가 만들었음. 이건 컴파일러가 continuation을 만드니까 컴파일러에게 받는것임.
sequence는 실행은 하지 않는다. 직접 하나하나 실행 해줘야한다.
suspendfun<T>suspendCoroutineUninterceptedOrReturn(block:(Continuation<T>)-> Any?): T //Continuation<T> 얘가 바로 코루틴내장되어져 있는 continuation 객체이다
overridesuspendfunyield(value: T){
nextValue = value
state = State_Ready
return suspendCoroutineUninterceptedOrReturn { c ->
nextStep = c
CORUTINE_SUSPENDED}}
CO : 일반적으로 코루틴 시스템이 적용되는 많은곳에서 수동으로 실행함수를 만들때 쓰는 기법
자바스크립트의 generator는 sequence와 똑같은 기능을 함. yield를 호출할 때마다 suspend 구간이 생기고 거기에 continuation을 만들지만 es6에서는 continuation의 제어권을 직접 주진 않음.
하지만 코루틴시스템에선 continuation 제어권을 인터페이스가 준다.
제너레이터에서는 yield를 원하는 타이밍에 할 수 없음. 왜냐면 동기 로직 안에서 yield넣을수 있을 뿐 이 안에서 ajax callback에 yield를 하는건 불가능. 동기화 로직 yield밖에 안된다. 왜냐면 우리가 yield 할 때 continuation을 제어권 없이 yield 하는 타이밍에 continuation이 resume이 되기 때문이다.
es8에서는 sync/await과 제너레이터를 결합해서 yield 타이밍을 미룰 수 있는 async generator가 있음 걔를 이용하면 sequence와 동등한 스펙을 구현할 수 있음.
제너레이터 안에서는 조종할 수 없지만 밖에서 제너레이터를 이터레이션을 돌리는 코드를 원하는 타이밍에 이터레이션 될 수 있도록 하는것이 코루틴의 CO의 원리이다.
제너레이터 또는 코루틴의 구현체가 단방향인 경우 코루틴 안에서 밖으로 이터레이션 값을 줄 수 있지만, 밖에서 코루틴 안으로 이터레이션 값을 줄수는 없다.
es6 제너레이터는 yield도 받고 next에 괄호 열고 값을 넣어줄 수도 있음. 그러면 코루틴에서는 못쓰는 a = yield(30) 이런 값을 받을 수 있음. 양방향 통신을 함. 밖에서 제너레이터 안으로 yield로 인자를 여러번 보낼 수 있음.
yield 상태에선 통신이 안되기 때문에 최초 함수의 인자는 받을 수 있지만 안에 있는 코루틴 이터레이션에서는 중간에 인자를 받을 수 없다.
class State{var result =""lateinitvar target:Promise<Response>// 현재는 null}
sequence{val s =State()//최초의 스테이트 탄생 시킴
s.target = window.fetch(Request("a.txt"))//window.fetch는 promise<Response>를 리턴하는 promiseyield(s)//yield로 state 객체 자체를 보냈음.
s.target = window.fetch(Request(s.result))//얘는 외부의 인자를 받아왔을 것임. 이것을 통해 외부와 대화를 할 수 있게 됬음. s객체의 result만 갱신하는 걸로 코루틴 안에서 밖깥쪽의 값을 얻어올 수 있음.yield(s)println(s.result)}funco(it:Iterrator<State>?=null, seq: SequenceScope<State>?=null){//sequenceScope의 타입이 state인 이유는 밑의 yield에서 s(State)를 보내기 때문에 타입 확정val iter = it ?:seq?.iterator()?:throwThrowable("invalid)if(iter.hasNext() iter.next().let{st ->
st.target.then{it.next()}.next{
st.result = it
co(iter)}})}//첫번쨰 인자는 없으면 알아서 null 처리 함co(sequence{val s =State()
s.target = window.fetch(Request("a.txt"))//window.fetch는 promise를 리턴함. a.txt를 날릴 껀데 여기 까지가 promise<Response>를 주는 promise이다yield(s)
s.target = window.fetch(Request(s.result))//코루틴 안쪽에서 밖의 값을 얻어올 수 있음. 이런 방식으로yield(s)println(s.result)})
위의 코드에서 중간에 대화를 할 수 있게끔 하기 위해 기본적인 target을 Promise<Response>로 지정
양방향 통신이 안되는 코루틴 같은 시스템에서는 직접 양방향 통신을 위한 메모리 공간을 갖고 있는객체를 외부에 출력해 줘야지만 그 객체의 속성을 안에서 이용할 수 있음.
그래서 yield로 부터 리턴값을 받아온건 아니지만 s의 속성으로 받아올 수 있음.
하지만 위까지는 안쪽 사정이고 우리는 시퀀스를 이용하는 CO 함수를 만들고 싶은것이다.
막 점프하고 하는게 너무 복잡하고 그러니 간단하게 sync block방식으로 사용하게 끔 co가 나온것
co 코드의 SequenceScope<State> 가 sequence함수의 리턴값 왜 State냐면 yield 할떄 입력값이 State() 이기 때문.
co 코드의 iterator는 contination의 passing에 의해 iterator가 직접 있는 경우와 없으면 seq?.iterator()로 받아오고 있음. 근데 둘다 안줬으면 Throw를 날림
seq는 iteration을 return 함
iter.next()는 State 객체가 나옴 . 위의 seq 스테이트 객체를 반환하는 iteration을 갖고 있는 seq 이기 때문이다.
State는 Promise<Response>임.
suspend & coroutine
cps와 suspend 와 코루틴을 이용하여 섹션을 만들어 내서 continuation을 자동으로 만들어줄 수 있다라는 기본 개념을 알게 되었고,
이전까지완 지금부터는 다른데 위에서 배운 기저 시스템을 이용해서 async generator처럼 비동기로 실행되지만 section 구분을 자동으로 컴파일러에게 의뢰해서 구문들을 만들어내는 일종의 닉네임들과 여러가지 니모닉으로 이루어져 있는 시스템을 제안함
결국 여기나오는 것들은 sequence와 continuos resume으로 다 번역이 됨 하지만 이것 깊숙히 감쳐줘 있고 이걸 추상화 하여 추상화한 객체끼리 통신들을 그 위에층에 만든것임 결국 위에층이 밑에 층에 깔린걸로 바뀜
위에서는 아얘 다른 언어로 만들어 놨음.(DSL)이걸 이해하기 위해선 윗쪽의 것들을 다 이해 해야한다.
코틀린 코틀린 생태계에서 나오는 coroutines는 위에서 나왔던 coroutine과는 다른 애다 고유 명사임
서브루틴이란 반복된 로직이 있는 경우 함수안에 가둬놓고 재활용해서 호출함으로 써 재활용 하는것
서브루틴 특징으로 인자와 리턴값이 있어서 서브루틴 내부가 위에서 아래로 한번 실행되는게 특징 이었음. 얘는 한번 돌고 끝이남
코루틴은 함수의 진입접과 리턴은 있을지 몰라도 중간에 계속해서 빠져나오고 진입할 수 있는 yield라는 공간들이 있어서 함수의 처음 호출 이외에도 여러번 진입 나오기 진입 나오기를 반복할 수 있는 스타일을 코루틴이라고 부름. co(복수) routine
kotlin coroutines
가장 핵심 적인 내용은 coroutineContext get(key):Element : 엘리먼트 라는 애의 컨테이너다 라고 생각하면 된다.
코루틴 컨텍스트가 하나 있으면 여기에 element를 잔뜩 집어넣을 수 있음. map 이랑 set 같은 놈임
그래서 coroutineContext element를 의존한다. 보유도 하고 있다. corutineContext -> element
그래서 coroutineConext == element 라고 해도 됨
하지만 그 coroutineContext가 element의 구상체임에 불구하고 다른점은 element의 컨테이너 이기도 하다
element를 job과 dispatcher가 상속을 함.
dispatch : 아까 우리가 만든 sequence가 아니라 코어 함수에 해당하는 실행기. 얘각 바로 실행해주는애. 우리가 만든애는 실행기가 아니라 sequence를 실행해줄 코어 함수가 필요 했음 .
코루틴 컨텍스트는 시퀀스 마냥 여러개의 이터레이션 객체들을 소유하고 그것을 실행하는 코어함수인 dispatcher를 소유하는 객체임.
대부분의 코루틴컨텍스트는 하나의 dispatcher와 다수의 job으로 이루어져있음.
서스펜드 코루틴 섹션을 만들어주는 시스템과 코어라는 이터레이션 실행기를 객체로 역활모델로 추상화 한게 코루틴 컨텍스트가 걔내들을 다 소유하는 컨테이너가 되고 이안에 실행기의 디스패처와 각각 이터레이션 나타내게 되는 잡들을 집어 넣게 되는것임.
job은 하나의 이터레이션을 추상화했기 때문에 대략 얼추 비슷한 기능을 갖고 있는 메소드가 제공된다 아까는 switch로 나눈 구문이 무조건 실행됬는데 이 안의 메소드 호출에 따라서 실행할지 말지 대기할지 추상화를 해놓은것임 이 일이 끝난 후에 continuation의 resume을 부르는 것은 당연한대 resume 부르는 행위를 언제할지를 추상화 시켜놔서 우리에게 추상화된 개념으로 제공해줌 하나의 이터레이션에 해당하는 녀석임
하지만 sync 로직만 커버함
처음 만든 sequence는 syn 로직이었는데 두번째로 한 promise는 밖에 있는 co 실행기가 then을 통해 resume을 시켰기 때문에 비동기 적으로 구현됬음. 하지만 자바는 promise와 비슷한 completedFuture라는 추상객체가 있음.
job을 상속해서 deferred라는 애를 만듬 얘는 then과 마찬가지로 await하는 시점에 모든 비동기가 해결 됬을때 resume을 호출해 주는 job으로 부터 상속받은 비동기를 처리하기 위한 것 promise와 비슷함
yield를 상수값으로 할 경우 동기적으로 내보내면 되지만 promise같은 애가 개입하면 아까 co에서 promise.then을 처리한 다음에야 우리가 co를 다시 호출할 수 있었음.
마찬가지로 힌트를 줘야함. 이 이터레이션이 동기적인 명령인지 아니면 비동기의 완료시점에 resume을 해줘야하는지 알아야함 내부적으로 이것을 추상화하고 있는 객체가 deffered
deferred는 then과 마찬가지고 await하는 시점에 비동기가 모두 해소됬을 때 resume을 해소해주는 promise then을 똑같이 해준것 같은 job을 상속받은 비동기를 처리하기 위한 객체임
동기적인 iteration은 다 job에 때려박고, 비동기 적인iteration은 다 deferred에 때려박음
deferred에 때려박으면 await 시점에 resume이 일어나고 job은 바로 start가 일어나서 join될때까지 바로 실행 되는것임
job과 deffered는 coroutine builder라는 애로 만든다.
launch를 이용하면 job을 만들 수 있고, async를 이용하면 deffered를 만들 수 있음.
둘중 하나를 만들어 coroutineContext에 빡빡 밟아주고 ..?? 실행하면 얘가 디스패처로 통해 자기가 쌓아놓은 job과 deferred를 co처럼 돌아가면서 이터레이션 실행한다.
그럼 job과 deffered로 만든 이 생태계를 아까처럼 switch 문으로 나눠주는 컴파일 과정이 있어야되는데 여기서 job과 deffered를 실행기인 dispatcher로 실행할 거지만 니가 내가만든 동기 코드를 job이나 deffered에 이 시점을 바탕으로 스위치 문을 다 state문으로 나눠 continuation 객체까지 다 연결해 줘야되 라는 지시를 컴파일러에게 해줘야하는데 .
이 동네 전체를 한꺼번에 suspend coroutine 하라는 키워드를 지정할 수 있는데 그것이 바로 suspend이다.
함수 앞에 suspend를 붙히면 그 함수 전체는 job 또는 deferred의 기준으로 자동으로 continuation section을 컴파일러가 만들어줌
우리는 cps 스타일 위에 sequence 시스템이 어떻게 움직이는지 suspend coroutine 함수가 시스템에 어떤 영향을 끼치는지 그 시스템을 추상화 해서 어떠한 구조로 만들어서 언어가 그위에 구축되어져 있는지 배움
밑의 코드는 지금까지의 지식을 바탕으로 아까 sequence가 만들어내던 yield 함수가 suspend coroutine을 쓰는걸 직접 써보자는 것임 직접 우리가 yield함수를 흉내내서
밑의 함수는 컴파일러에게 명령을 내리는 함수이다. 얘가 등장하는 순간 continuation switch section을 나눠줌 suspendCoroutine은 인자로 continuation을 받음 컴파일러가 만들어준 continuation을 받을 수가 있음. 이 섹션을 더이상 진행할지 말지는 cont.resume으로 결정한다. resume일때 값을 리턴한다. block은 T를 넘길수 있는 함수를 인자로 받는 함수이다. 그렇기 때문에 밑의 코드에 블록 안에 있는 cont.resume(it)는 T를 넘길 수 있는 함수이다.
suspend 함수는 컴파일러가 이 전체를 suspendCoroutine이나 suspendCoroutine을 추상화 하고 있는 job 또는 deferred를 기준으로 switch를 나눠줌
constsum=n=>{let sum =0;for(let i =1; i <= n; i++) sum += i;return sum;};sum(100);
sync non-block
sync : 서브루틴이 리턴 값으로 반환
non-block : 즉시 플로우 제어권을 반환
constsum=n=>{const result ={isComplete:false};requestAnimationFrame(_=>{let sum =0;for(let i =1; i <= n; i++) sum += i;
result.isComplete =true;
result.value = sum;});};const result =sum(100);while(!result.isComplete);
console.log(result.value);
async block
async : 서브루틴이 콜백을 통해 값을 반환
block : 즉시 플로우 제어권을 반환하지 않음
예
node의 async로 block인 jdbc를 사용 ?
constsum=(n, f)=>{let sum =0;for(let i =1; i <= n; i++) sum += i;returnf(sum);};sum(10, console.log);
console.log(123);//55 → 123
async non-block
async : 서브루틴이 콜백을 통해 값을 반환
non-block : 즉시 플로우 제어권을 반환
constsum=(n, f)=>{requestAnimationFrame(_=>{let sum =0;for(let i =1; i <= n; i++) sum += i;f(sum);});};sum(10, console.log);
console.log(123);//123 → 55
blocking non-blocking 과 sync 와 async는 논의하는 관점이 좀 다르다 실제로 나의 생각이지 정확한 것은 아니다 고민해 보고 수정할 생각이다.
blocking non-blocking은 실행 방식에 따른 관점이다. 실제로 sync 방식으로 할 경우 blocking을 피할 수가 없다. async로 할 경우, 다른 스레드에 실행을 시키기 떄문에 현재 스레드는 non-blocking으로 동작 할것이다.
sync async는 메모리에 적재된 명령을 순서대로 실행하는 실행 순서에 대한 관점이다. async로 할 경우, 해당 콜백이 언제 실행될지 우리는 예측을 할 수가 없다. 다른 스레드에서 돌고 있기 때문에. 만약에 다른 스레드에서 콜백이 실행 되도록 해놓는다면, 당연히 sync는 메모리에 적재된 순서대로 실행하지 않을것이다.
밑의 코드를 보면 main을 Coroutine$main.doResume이라는 함수를 자동으로 만들어 내는데 자동으로 만들어내는걸 보면 do 하면서 본인의 state_0 본인이 continuation임 println(a)하고 그다음 result 안에 task를 넣어줌. task 가 continuation this를 받아감. 이 task가 이때 컨티뉴에이션 객체를 이때 받는거랑 마찬가지임 . 우리가 만든 테스크가 어떻게 continuation 객체를 받냐고 시스템이 만든 continuation 객체가 여기에 온것임. suspend 함수가 이렇게 나누는 것임.
실제로는 두덩어리임 println(‘a’)까지는 suspend coroutine 부분이 아니라 println(tast{it(‘b’)}) 까지가 한덩어리고
우리는 suspend coroutine에서 continuation가 이 로직의 바로 다음번으로 전개될 수 있는 방법이란걸 알고 있으니 타임아웃을 만들어 볼 수 있다.
time아웃 원리는 원하는 시간을 t로 받고 리턴을 할 필요가 없음.
쟤는 task함수를 이용해서 suspendCoroutine에 진입 한것임
task는 window.setTimeout에 t만큼 돈 다음 실행되는 람다 안에서 받아온 it (위 코드의 cont.resume(it))에 Unit을 줬음.
그래서 밑에 애는 시간만 끌고 resume을 해준것이다.
a가 출력되서 windowSetTimeout 1초가 지난 후에나 b가 출력됨
suspend의 resume이 저때 발생하기 때문이다.
timeout은 리턴값이 없지만 window.setTimeout을 끌어주는 효과가 생김
밑의 코드를 보면 async non-blocking 함수를 sync blocking 형태로 사용할 수 있게 되었음.
a와 b가 동기라면 바로 나와야 하는데 중간에 비동기이면서 async 스타일을 사용하고 있는 setTimeout이 껴들어 있는데도 불구하고 sync blocking 스타일로 쓸 수 있게 됬다.
밑과 같이 써서 a 다음 비동기 적으로 1초가 지나고 b가 호출 되었음.
여기 1초는 동기형으로 blocking 하고 있지 않음 . 여기서 유아이 다 움직인다.
timeout이 블로킹 하고 있지 않는데 밑의 코드는 순서대로 실행이 된다.
코루틴은 언어의 특정 문법을 사용하면 컴파일러가 스위치 컨티뉴에이션으로 바꿔주는 행위이다 .
suspend fun timeout(t:Int):Unit = task{window.setTimeout({it(Unit)}, t)}
suspend fun main(){println("a")timeout(1000)println("b")}
suspendCancellableCoroutine
suspsnedCoroutine와 다르게 예외처리를 할 수 있음.
밑의 차이는 resumeWithException 를 사용할 수 있다는 것임
우리는 suspendCoroutine 섹션에서 우리가 원하는 시점에 continuation resume을 때릴 수 있다는 사실을 알고 있다.
그러면 continuation resume말고 promise 면 reject일때 어떻게 할건가? resumeWithException으로 처리하면 된다.
continuation객체에 일반적인 레포트를 받아주는 resume과 예외적인 레포트를 받아주는 resumeWithException이 있다.
어떻게 window.fetch(Request(“a.txt)).await()는 response이다. 왜냐면 window.fetch(Request(“a.txt))가 Promise<response>를 리턴할 것이고, await의 리턴값을 보면 T를 리턴하기 때문에 response를 리턴한다.
reponse1.text()하면 String을 반환하는 promise 객체가 나올 것임. await 을 하면 string 나올것이고 .
javascipt와 굉장히 유사하다 . async라는 키워드 대신에 suspend를 썼고 await라는 언어 수준의 여기에 있는 수신함수를 치환한것 뿐이다.
다른 함수는 continuation의 resume에 접근할 수 없게 만든다. 코틀린은 그와 다르게 suspendCoroutine만 부르면 coroutine iterator 섹션으로 바꿔준다. 그리고 니가 맘대로 하게 해주고 resume 또한 마음대로 할 수 있음 .
cps를 이해하고 cps가 실제적으로 섹션구분으로 continuation 섹션을 만들어내는 과정도 이해하고 얘의 실행기도 이해하면 코루틴이 쉽다.
그러면 실제 suspend 안에서 launch나 async로 만들어져 있는 job이 continuation section을 나눠주는건 아니고, job같은 경우 내부에서 starting 모드를 결정할 수 있다.
인자 값으로 context를 받고 빌더함수가 만드는 job을 어떤 context(container contextContainer는 element 컨테이너이고 job도 element이다.)에 넣을건지에 대한 거다.
아래서 넣은 EmptyCoroutineContext는 dispatcher도 아무 element도 없을것 같지만 시스템이 인지해서 이게 empty라면 기본 디스패처를 넣어주고 기본 잡을 넣어준다.
인자값 start는 DEFAULT일 경우에 런치로 만들어진 즉시 start를 때림
job은 리턴값이 없음 안에 있는 블록을 무조건 실행한다. 그럼 안에 있는 블록도 suspended 블록이기 때문에 다시 suspend 요소들을 가져올 수 있다. 여기에 동기화 로직만 들어가는게 아니다 suspend안에 suspend가 있으면 switch문 안에 어떤 case에 들어가는 switch문이 생긴다. 왜냐면 하나의 suspend section은 계속해서 do Resume에서 봤던것 처럼 switch문으로 바뀌기 때문 switch 문에 또 switch문이 생길 것임.
두번째엔 context에 Dispatchers.Default를 넣었는데 dispatcher는 element이긴 한데 coroutine context는 아니었음. 그걸 launch에 넘겨주면 얘를 담고 있는 빈 context를 만들어줌. 그냥 dispatcher만 넣으면 이것만 넣어서 컨텍스트를 만들어준다. 이 디스패처에 따라 어떤 실행기가 이터레이션을 처리할지가 결정이 된다. 실행기가 a스레드와 b스레드에 실행되는게 있다면 어떤 디스패처를 고르냐에 따라 어떤 스레드에서 처리될지 결정된다. 스레드 환경에서는 보통 디스패처가 어떤 스레드를 쓰냐가 중요해지기 때문에 스레드를 고르는 행위나 코루틴 풀에서 작동시킬 거냐 에 따라 달라진다.dispatcher가 스레드를 결정하기 때문에 중요하다.
안에서 suspend 구문을 지원하긴 하지만 무조건 위에서 아래로 흐르는 로직 이외는 지원하지 않는다.
리턴값이 없고 안이 무조건 실행된다. 그래서 launch 이다. 그냥 쏘면 걍 안이 실행될 뿐 더이상 할 수 있는게 없다. 얘는 continuation resume을 쓸수가 없음. 코틀린 코루틴 생태계에서 job이나 deffered를 쓰면 문제점은 우리가 컨티뉴에이션의 리쥼에 대한 통제권을 잃는다.그래서 블록 안에 동기적으로 실행되는것밖엔 안된다. 블록 안에서 ajax 콜백에서 리쥼같은건 못함 걍 흘러내림
suspendCoroutine을 사용하는 블록을 task로 추상화를 위에서 시켰었는데 그럼 나와있는 빌더를 이용한 글로벌 스코프 async나 global scope launch도 위와같이 추상화 시킬 수 있음.
async를 보면 suspend CoroutineScope를 수신함수로 받는 인자를 받아서 오른쪽에 직접 블록을 넘겨주면 된다.
async launch task를 이용하면 손쉽게 우리가 원하는 yield를 정리해서 끼워넣을 수 있음. 그럼 위에서 아래로 읽으면 비동기로 쳐리되지만 sync blocking 으로 보인다.
다른 비동기 로직들은 callback을 이용하는데 이것의 나쁜점은 어휘공간이 보존되지 않는다는 건데 콜백 안에서 인자로 받지 않으면 인자로 가져올 수 없음.
suspend함수를 쓰면 여기 만들어진 지역 변수가 이 모든 비동기로직이 한꺼번에 공유되기 때문에 손쉽게 클로저에 도움받지 않고 동기로직처럼 짤 수 있다.
2.Feelings
코틀린 코루틴에 대해서 정말 4일 전까지만 해도 영상을 듣거나 책을 읽어도 이해를 하지 못했는데, 영상을 여러번 돌려보고 하나하나 무슨말을 하는지 따져가면서 반복하다 보니 눈에 들어오기 시작했다.
오늘 알고리즘 문제를 풀면서 수학문제를 만났는데, 정말 알고리즘 문제를 다양하게 많이 풀어서 어떤 유형의 수학문제들이 나오는지 익히고 수학 공부가 필요함을 느꼈다.
3.Findings
java의 future는 isCompleted를 while문으로 사용한다면 sync nonblocking이고 future.get으로 사용한다면 block async 이다. 전자는 future.isCompleted를 계속해서 요청해서, 백그라운드 스레드가 처리됬는지 확인하고, future.get 은 callback과 비슷한 놈이다. async로 요청을 하지만, 실행한 함수를 블로킹 걸고 끝나면 제어권을 실행함수로 넘겨주면서 콜백으로 get에 원하는 값이 존재하도록 하기 때문이다.
오늘은 sync async block non-blocking 에 대해서 더 공부를 할 수 있었다.
java의 completedFuture는 async nonblocking 방식이다. 자바스크립트의 promise와 유사한데 반제어 상태로 async non-blocking 방식을 구현한다. completedFuture를 사용하면 백그라운드는 실행되고 제어권도 실행한 함수로 돌아온다. 그래서 뒤에 코드를 계속해서 작성할 수 있다. 그리고 이후에 completedFuture에 접근을 할 경우, 백그라운드 실행이 완료 됬다면, 값을 반환하고, 완료되지 않았다면, 그 자리에서 기다리는 방식이다.
kotlin 에서 operator로 getValue와 setValue를 넣어준다면 그것은 by로 delegate 한것과 동일하다.
by로 deligate를 할 경우에, 그냥 a.b를 부를경우 b를 getValue로 가져오고 a.b = 1 을 할 경우 setValue로 값을 설정한다. 그래서 이 두 오퍼레이터에 커스텀을 해서 처리할 수가 있다.